import type { Metadata } from 'next'; import Link from 'next/link'; import { notFound } from 'next/navigation'; import { t } from '@/i18n'; import { api, isNotBuilt, safe } from '@/lib/api'; import { formatDate } from '@/lib/format'; import { jsonLd, seoTitle } from '@/lib/seo'; import { routes } from '@/lib/site'; import { STORIES, storyBySlug, storyChartCount, storyIndicators } from '@/lib/stories'; import { topicById } from '@/lib/topics'; import { NotBuiltState } from '@/components/data/empty-state'; import { PageHeader } from '@/components/explore/page-header'; import { JsonLd } from '@/components/platform/json-ld'; import { loadStoryData } from '@/components/stories/resolve'; import { StoryBlocks } from '@/components/stories/story-blocks'; export const revalidate = 3600; type Params = { slug: string }; export function generateStaticParams(): Params[] { return STORIES.map((s) => ({ slug: s.slug })); } export async function generateMetadata({ params }: { params: Promise }): Promise { const { slug } = await params; const s = storyBySlug(slug); if (!s) return { title: t('stories.notFound'), robots: { index: false } }; const title = seoTitle.story(s.title); const canonical = routes.story(s.slug); return { title, description: s.standfirst, alternates: { canonical }, openGraph: { title: `${title} — ${t('site.name')}`, description: s.standfirst, url: canonical, type: 'article', publishedTime: s.published }, twitter: { card: 'summary_large_image', title, description: s.standfirst }, }; } export default async function StoryPage({ params }: { params: Promise }) { const { slug } = await params; const story = storyBySlug(slug); if (!story) notFound(); let health; try { health = await api.health(); } catch (e) { if (isNotBuilt(e)) return ; throw e; } if (health.status === 'empty') return ; const data = await loadStoryData(story); const indicators = storyIndicators(story); const indicatorRes = await Promise.all(indicators.map((s) => safe(api.indicatorMap(s, {})))); const indicatorNames = indicators.map((s, i) => ({ slug: s, name: indicatorRes[i]?.indicator.name ?? s })); const sources = Array.from(new Set(indicatorRes.map((m) => m?.provenance?.source_name).filter((x): x is string => !!x))); const others = STORIES.filter((s) => s.slug !== story.slug); const topics = story.topics.map((tp) => topicById(tp)?.short ?? tp); return ( <>

{t('stories.sources')}

{t('stories.sourcesNote', { run: health.run_id ?? '', date: formatDate(health.built_at) })}

{sources.length ?

{sources.join(' · ')}

: null}

{t('stories.indicators')}

    {indicatorNames.map((i) => (
  • {i.name}
  • ))}

{t('stories.method')}

{t('stories.methodText')}

{t('stories.more')}

    {others.map((s) => (
  • {s.title} {s.standfirst}
  • ))}
); }